/*-
 * UD.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 107 $
 * $LastChangedDate: 2007-02-21 14:37:29 +0000 (Wed, 21 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/UD.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import static com.googlecode.attention.sms.pdu.Codec.*;
import static com.googlecode.attention.util.SMSCharacterSet.*;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import com.googlecode.attention.util.Convert;
import com.googlecode.attention.util.IntArrayBuilder;


/**
 * TP-User Data (TP-UD).
 * <BR>
 * <BR>The length of the TP-User-Data field is defined in the PDU"s of the
 * SM-TL. The TP-User-Data field may comprise just the short message itself or
 * a Header in addition to the short message depending upon the setting of
 * TP-UDHI. Where the TP-UDHI value is set to 0 the TP-User-Data field
 * comprises the short message only, where the user data can be 7 bit
 * (default alphabet) data, 8 bit data, or 16 bit (UCS2 [24]) data. Where the
 * TP-UDHI value is set to 1 the first octets of the TP-User-Data field
 * contains a Header in the following order starting at the first octet of
 * the TP-User-Data field. Irrespective of whether any part of the User Data
 * Header is ignored or discarded, the MS shall always store the entire TPDU
 * exactly as received.
 *
 * @author Stein Gunnar Bakkeby
 */
public class UD extends IntArrayBuilder {

  private static final long serialVersionUID = 6307080389089799309L;

  /* Field values */
  private int[] udh;
  private DCS   dcs;
  private UDHI  udhi;
  private int   udl;
  private Codec codec; // by default singleton will be retrieved
  
  /** Max length of user-data field, in octets. */
  public static final int MAX_SPACE_USER_DATA = 140; // octets
  /** Max length of UCS2 data, in characters.<BR>(1 char = 2 octets) */
  public static final int MAX_SPACE_UCS2      =  70;
  /** Max length of 8-bit data, in characters.<BR>(1 char = 1 octet) */
  public static final int MAX_SPACE_8_BIT     = 140;
  /** Max length of 7-bit data, in characters.<BR>(8 chars = 7 octets) */
  public static final int MAX_SPACE_7_BIT     = 160;
  
  /**
   * Default constructor, creates an empty User-Data field.
   */
  public UD() {
    super(40);
    udhi = UDHI.SHORT_MESSAGE_ONLY;
    udh  = new int[0]; // TODO consider here using UDH object, also the whole
                       //      of UDH / UDIE must be rewritten and confirmed
    dcs  = new DCS();
    udl  = 0;
    codec = Codec.getInstance();
  }
  
  /**
   * Default constructor, creates an empty User-Data field.
   */
  public UD(final String text, final DCS dcs) {
    udl  = 0;
    this.dcs = dcs;
    setUD(text);
    udhi = UDHI.SHORT_MESSAGE_ONLY;
    udh  = new int[0]; // TODO consider here using UDH object, also the whole
                       //      of UDH / UDIE must be rewritten and confirmed
    codec = Codec.getInstance();
  }
  
  /**
   * Constructor, creates the User-Data field.
   * 
   * @param ud The raw data of the UD field (excluding User-Data-Length)
   * @param udl The user-data length
   * @param udhi The User-Data-Header-Indicator 
   * @param dcs The Data-Coding-Scheme for this data / message
   */
  public UD(final int[] ud, final int udl, final UDHI udhi, final DCS dcs) {
    this.dcs  = dcs;
    this.udhi = udhi;
    this.codec = Codec.getInstance();
    if (udhi == UDHI.HEADER_PRESENT) {
      boolean septets = false;
      switch (dcs.getGroup()) {
      case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
        break;
        
      case DCS.GROUP_GENERAL_DATA_CODING:
      case DCS.GROUP_AUTOMATIC_DELETION:
      case DCS.GROUP_MESSAGE_CODING_CLASS:
        switch (dcs.getMessageCoding()) {
        case DCS.MESSAGE_CODING_8_BIT_DATA:
        case DCS.MESSAGE_CODING_UCS2:
          break;
        
        case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
        case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
        default: /* Falls through */
        }/* Falls through */
        
      case DCS.GROUP_RESERVED_1000: /* All reserved groups */
      case DCS.GROUP_RESERVED_1001: /* are assumed to      */
      case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
      case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
      case DCS.GROUP_MESSAGE_WAITING_DISCARD:
      case DCS.GROUP_MESSAGE_WAITING_STORE:
      default:
//        double res = (double) length * 7 / 8;
//        length = (int) res + (res > (int) res ? 1 : 0);
        septets = true;
        break;
      }
      
      final int headerLength = ud[0];
      udh = new int[headerLength];
      
      System.arraycopy(ud, 1, udh, 0, headerLength);
      final int newUDLength = ud.length - headerLength - 1;
      final int[] newUD = new int[newUDLength];
      System.arraycopy(ud, 1 + headerLength, newUD, 0, newUDLength);
      final int fillBits = 6 - headerLength * 8 % 7;
      final double res = ((headerLength + 1) * 8 + fillBits) / 7;
      if (septets) {
        set(decompress(newUD, udl - (int)res, dcs, fillBits));
      } else { // octets
        set(decompress(newUD, udl - headerLength - 1, dcs, 0));
      }
    } else { // no header
      this.udh = new int[0];
      set(decompress(ud, udl, dcs, 0));
    }
    this.udl = udl;
    //updateUDL(endpoint, true);
  }
  
  /**
   * This will calculate the space left.
   * 
   * @return The number of space left in this user-data field.
   */
  public int getSpaceLeft() {
    switch (dcs.getGroup()) {
    case DCS.GROUP_GENERAL_DATA_CODING:
    case DCS.GROUP_AUTOMATIC_DELETION:
      if (dcs.isTextCompressed()) {
        return MAX_SPACE_USER_DATA - udl;
      }
      
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        return MAX_SPACE_8_BIT - udl;
        
      case DCS.MESSAGE_CODING_UCS2:
        return MAX_SPACE_UCS2 - udl;
      
      case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        return MAX_SPACE_7_BIT - udl;
      }
      
    case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
      return MAX_SPACE_UCS2 - udl;

    case DCS.GROUP_MESSAGE_CODING_CLASS:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        return MAX_SPACE_8_BIT - udl;
        
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        /* Default alphabet, so falls through! */
      }
      /* Falls through */
      
    case DCS.GROUP_RESERVED_1000: /* All reserved groups */ 
    case DCS.GROUP_RESERVED_1001: /* are assumed to      */
    case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
    case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
    case DCS.GROUP_MESSAGE_WAITING_DISCARD:
    case DCS.GROUP_MESSAGE_WAITING_STORE:
    default:
      return MAX_SPACE_7_BIT - udl;
    }
  }
  
  /**
   * This will return the space used.
   * <BR>
   * <BR>TP-User-Data-Length (TP-UDL).
   * <BR>If the TP-User-Data is coded using the GSM 7 bit default alphabet,
   * the TP-User-Data-Length field gives an integer representation of the
   * number of septets within the TP-User-Data field to follow. If the 7bit
   * default-alphabet extension mechanism is used within the TP-User-Data
   * (see 3GPP TS 23.038), the actual number of characters in the message
   * shall be less than the number of septets. If a TP-User-Data-Header field
   * is present, then the TP-User-Data-Length value is the sum of the number
   * of septets in the TP-User-Data-Header field (including any padding) and
   * the number of septets in the TP-User-Data field which follows. If the
   * TP-User-Data is coded using 8-bit data, the TP-User-Data-Length field
   * gives an integer representation of the number of octets within the
   * TP-User-Data field to follow.
   * <BR>
   * <BR>If a TP-User-Data-Header field is present, then the
   * TP-User-Data-Length value is the sum of the number of octets
   * in the TP-User-Data-Header field and the number of octets in the
   * TP-User-Data field which follows.
   * <BR>
   * <BR>If the TP-User-Data is coded using UCS2 data, the TP-User-Data-Length
   * field gives an integer representation of the number of octets within
   * the TP-User-Data field to follow. If a TP-User-Data-Header field is
   * present, then the TP-User-Data-Length value is the sum of the number
   * of octets in the TP-User-Data-Header field and the number of octets
   * in the TP-User-Data field which follows.
   * <BR>
   * <BR>If the TP-User-Data is coded using compressed GSM 7 bit default
   * alphabet or compressed 8 bit data or compressed UCS2 data, the
   * TP-User-Data-Length field gives an integer representation of the number
   * of octets after compression within the TP-User-Data field to follow.
   * If a TP-User-Data-Header field is present, then the TP-User-Data-Length
   * value is the sum of the number of uncompressed octets in the
   * TP-User-Data-Header field and the number of octets in the compressed
   * TP-User-Data field which follows.
   * <BR>
   * <BR>For other Data Coding Schemes, see 3GPP TS 23.038. If this field
   * is zero, the TP-User-Data field shall not be present.
   * 
   * @return The space used in this User-Data field.
   */
  public int getUDL() {
    return udl;
  }
    
  /**
   * This will return the User-Data Octets.
   * <BR>
   * <BR>This method will encode or compress the user-data according to
   * the specified Data-Coding-Scheme (DCS).
   * 
   * @return The User-Data (User-Data-Length not included)
   * @see #getUDL()
   */
  public int[] getEncoded() {
    final int[] ud = toArray();
    if (ud.length == 0) return ud;
    int udl = ud.length; // num of octets/septets
    final IntArrayBuilder uData = new IntArrayBuilder(udl + 5);
    final int udhl = udh.length;
    final boolean udhIndicated = (udhi == UDHI.HEADER_PRESENT && udhl != 0);
    int fillBits = (udhIndicated ? 6 - udhl * 8 % 7 : 0);
    switch (dcs.getGroup()) {
    case DCS.GROUP_GENERAL_DATA_CODING:
    case DCS.GROUP_AUTOMATIC_DELETION:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        if (dcs.isTextCompressed()) {
          uData.set(codec.compress8bit(ud));
        } else {
          uData.set(encode8bit(ud));
        }
        break;
        
      case DCS.MESSAGE_CODING_UCS2:
        if (dcs.isTextCompressed()) {
          uData.set(codec.compressUCS2(ud));
        } else {
          uData.set(encodeUCS2(ud));
        }
        udl *= 2; // 2 ints per letter (e.g. 2 octets)
        fillBits = 0;
        break;
      
      case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        if (dcs.isTextCompressed()) {
          uData.set(codec.compress7bit(unicodeToGSM(ud)));
        } else { // text not compressed 
          int[] gsmEncoded = unicodeToGSM(ud);
          if (gsmEncoded.length > ud.length) {
            udl += (gsmEncoded.length - ud.length);
          }
          uData.set(encode7bit(gsmEncoded, fillBits));
        }
        break;
      }
      break; // GROUP_GENERAL_DATA_CODING & GROUP_AUTOMATIC_DELETION

    case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
      /* Uncompressed UCS2 encoding. */
      uData.set(encodeUCS2(ud));
      udl *= 2; // 2 ints per letter
      fillBits = 0;
      break; // GROUP_MESSAGE_WAITING_STORE_UCS2

    case DCS.GROUP_MESSAGE_CODING_CLASS:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        uData.set(encode8bit(ud));
        break;
        
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        /* Default alphabet, so falls through! */
      }
      /* Falls through */
      
    case DCS.GROUP_RESERVED_1000: /* All reserved groups */ 
    case DCS.GROUP_RESERVED_1001: /* are assumed to      */
    case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
    case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
    case DCS.GROUP_MESSAGE_WAITING_DISCARD:
    case DCS.GROUP_MESSAGE_WAITING_STORE:
    default:
      int[] gsmEncoded = unicodeToGSM(ud);
      if (gsmEncoded.length > ud.length) {
        udl += (gsmEncoded.length - ud.length);
      }
      uData.set(encode7bit(gsmEncoded, fillBits));      
      break; // RESERVED_10XX, MESSAGE_CODING, WAITING_DISCARD & WAITING_STORE
    }
    
    /* End of encoding */
    if (udhIndicated) {            // User-Data-Header-Indication
      // TODO not sure about the length here in relation to 8-bit encoding,
      // UCS2 encoding, and compressed messages. In these cases the UDL
      // represents the number of octets rather than septets.
      udl += ((udhl + 1) * 8 / 7); //  --> septets
      if (fillBits != 0) udl++;
      
      uData.insert(0, udh);  // User-Data-Header
      uData.insert(0, udhl); // User-Data-Header-Length
    }
    this.udl = udl;
    return uData.toArray();
  }
  
  private int[] decompress(final int[] ud,
                           final int udl,
                           final DCS dcs,
                           final int fillBits) {
    final int[] uData;
    DCS_GROUP:
    switch (dcs.getGroup()) {
    case DCS.GROUP_GENERAL_DATA_CODING:
    case DCS.GROUP_AUTOMATIC_DELETION:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        if (dcs.isTextCompressed()) {
          uData = codec.decompress8bit(ud).toArray();
        } else {
          uData = decode8bit(ud);
        }
        break;
        
      case DCS.MESSAGE_CODING_UCS2:
        if (dcs.isTextCompressed()) {
          uData = codec.decompressUCS2(ud).toArray();
        } else {
          uData = decodeUCS2(ud);
        }
        break;
      
      case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        if (dcs.isTextCompressed()) {
          uData = gsmToUnicode(codec.decompress7bit(ud).toArray());
        } else { // text not compressed
          int[] var = decode7bit(ud, udl, fillBits);
          uData = gsmToUnicode(var);
        }
        break;
      }
      break; // GROUP_GENERAL_DATA_CODING & GROUP_AUTOMATIC_DELETION
      
    case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
      /* Uncompressed UCS2 encoding. */
      uData = decodeUCS2(ud);
      break; // GROUP_MESSAGE_WAITING_STORE_UCS2

    case DCS.GROUP_MESSAGE_CODING_CLASS:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        uData = decode8bit(ud);
        break DCS_GROUP;
        
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        /* Default alphabet, so falls through! */
      }
      /* Falls through */
      
    case DCS.GROUP_RESERVED_1000: /* All reserved groups */ 
    case DCS.GROUP_RESERVED_1001: /* are assumed to      */
    case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
    case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
    case DCS.GROUP_MESSAGE_WAITING_DISCARD:
    case DCS.GROUP_MESSAGE_WAITING_STORE:
    default:
      uData = gsmToUnicode(decode7bit(ud, udl, fillBits));
      break; // RESERVED_10XX, MESSAGE_CODING, WAITING_DISCARD & WAITING_STORE
    }
    
    /* End of encoding */
    this.udl = uData.length;
    return uData;
  }
  
  /**
   * This will set the UDH, to activate the header use the method
   * setHasHeader(boolean) and make sure the UDHI (in the first octet)
   * have the same setting.
   * 
   * @param udh The User-Data-Header
   * @see #setHasHeader(boolean)
   */
  public void setUDH(final UDH udh) {
    if (udh == null) {
      this.udh = new int[0];
      this.udhi = UDHI.SHORT_MESSAGE_ONLY;
    } else {
      final int oldudhl = this.udh.length;
      //final int[] oldUDH = this.udh;
  //    final int udhl = oldudhl * 8 % 7;
      this.udh = udh.toArray();
      udl += this.udh.length - oldudhl; // Subtract old udhl
//      if (udhl != 6) udl--;
//      if ((this.udh.length * 8 % 7) != 6) udl++;
      if (this.udh.length > 0) this.udhi = UDHI.HEADER_PRESENT;
    }
  }
  
  /**
   * Set this to true if the User-Data includes a header. Note that this
   * method automatically alters the value of the UDHI.
   * 
   * @param hasHeader True if the UD field has a header, set to false otherwise
   * @see #setUDH(UDH)
   */
  public void setHasHeader(final boolean hasHeader) {
    udhi = (hasHeader ? UDHI.HEADER_PRESENT : UDHI.SHORT_MESSAGE_ONLY);
  }
  
  /**
   * This will set the Data-Coding-Scheme of this User-Data field.
   * 
   * @param dcs The DCS
   */
  public void setDCS(final DCS dcs) {
    this.dcs  = dcs;
    udl = 0; // necessary as encoding may have changed
    updateUDL(endpoint, true);
  }
  
  /**
   * This will return the Data-Coding-Scheme of this User-Data field.
   * 
   * @return The DCS
   */
  public DCS getDCS() {
    return dcs;
  }
  
  /**
   * This will return the User-Data-Header-Indicator.
   * 
   * @return The UDHI
   */
  public UDHI getUDHI() {
    return udhi;
  }
  
  /**
   * This will set the User-Data (e.g. text/message).
   * 
   * @param data The User-Data (TP-UD)
   */
  public void setUD(final int[] data) {
    set(data);
    udl = 0;
    updateUDL(data.length, true);
  }
  
  /**
   * This will set the User-Data (e.g. text/message).
   * 
   * @param data The User-Data (TP-UD)
   */
  public void setUD(final String data) {
    set(data);
    udl = 0;
    updateUDL(data.length(), true);
  }

  /**
   * This will return the User-Data-Header object.
   * 
   * @return The UDH object
   */
  public UDH getUDH() {
    return new UDH(udh);
  }
  
  /**
   * This will convert the user data into a readable string.
   * 
   * @return The content of this message as a String
   * @see #getMessageAsIntArray()
   */
  public String getMessage() {
    return Convert.intToString(this.toArray());
  }
  
  /**
   * This will return the user data as an int array.
   * 
   * @return The content of this user data field as an int array.
   * @see #getMessage()
   */
  public int[] getMessageAsIntArray() {
    return this.toArray();
  }
  
  /**
   * This will remove the last <I>n</I> chars from the User-Data field.
   * 
   * @param numberOfChars The number of chars to remove
   */
  public void removeLast(final int numberOfChars) {
    super.removeLast(numberOfChars);
    if (endpoint == 0) {
      if (udhi == UDHI.HEADER_PRESENT) {
        udl = udh.length + 1;
      } else {
        udl = 0;        
      }
    } else {
      updateUDL(numberOfChars, false);
    }
  }
  
  /**
   * This will remove the last char added to the User-Data.
   * 
   * @return The integer that was removed.
   */
  public int removeLast() {
    final int output = super.removeLast();
    updateUDL(1, false);
    return output;
  }
  
  /**
   * This will return the compression codec used. Note that by default the
   * UD gets the compression codec from the Codec singleton.
   * 
   * @return The codec
   */
  public Codec getCodec() {
    return codec;
  }
  
  /**
   * This will set the compression codec to use. Use this method if a specific
   * compression codec is to be used instead of that delivered by the Codec
   * singleton.
   * 
   * @param codec The codec
   */
  public void setCodec(final Codec codec) {
    this.codec = codec;
  }
  
  /**
   * This will update the space used variable.
   * 
   * @param numberOfChars The number of chars to add / remove
   * @param plus True if add chars, false if remove chars
   */
  private void updateUDL(final int numberOfChars, final boolean plus) {
    switch (dcs.getGroup()) {
    case DCS.GROUP_GENERAL_DATA_CODING:
    case DCS.GROUP_AUTOMATIC_DELETION:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        if (dcs.isTextCompressed()) {
          udl = codec.compress8bit(toArray()).length();
        } else {
          udl += (plus ? numberOfChars : -numberOfChars);
        }
        break;
        
      case DCS.MESSAGE_CODING_UCS2:
        if (dcs.isTextCompressed()) {
          udl = codec.compressUCS2(toArray()).length();
        } else {
          udl += (plus ? numberOfChars : -numberOfChars);  
        }
        break;
      
      case DCS.MESSAGE_CODING_RESERVED: // assumed 7 bit default
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        if (dcs.isTextCompressed()) {
          udl = codec.compress7bit(unicodeToGSM(toArray())).length();
        } else {
          //numberOfChars = Convert.unicodeToGSM(chars).length;
          udl += (plus ? numberOfChars : -numberOfChars);
        }
        break;
      }
      break; // GROUP_GENERAL_DATA_CODING & GROUP_AUTOMATIC_DELETION
      
    case DCS.GROUP_MESSAGE_WAITING_STORE_UCS2:
      udl += (plus ? numberOfChars : -numberOfChars);
      break;

    case DCS.GROUP_MESSAGE_CODING_CLASS:
      switch (dcs.getMessageCoding()) {
      case DCS.MESSAGE_CODING_8_BIT_DATA:
        udl += (plus ? numberOfChars : -numberOfChars);
        break;
        
      case DCS.MESSAGE_CODING_7_BIT_DEFAULT:
      default:
        /* Default alphabet, so falls through! */
      }
      /* Falls through */
      
    case DCS.GROUP_RESERVED_1000: /* All reserved groups */ 
    case DCS.GROUP_RESERVED_1001: /* are assumed to      */
    case DCS.GROUP_RESERVED_1010: /* use the GSM 7-bit   */
    case DCS.GROUP_RESERVED_1011: /* default alphabet.   */
    case DCS.GROUP_MESSAGE_WAITING_DISCARD:
    case DCS.GROUP_MESSAGE_WAITING_STORE:
    default:
      //numberOfChars = Convert.unicodeToGSM(chars).length;
      udl = (plus ? numberOfChars : -numberOfChars);
      break; // RESERVED_10XX, MESSAGE_CODING, WAITING_DISCARD & WAITING_STORE
    }
  }
  
  /**
   * @serialData udhi, (? udh), dcs, udl, codec
   */
  @Override
  public void readExternal(final ObjectInput in) throws IOException,
                                                 ClassNotFoundException {
    super.readExternal(in);
    this.udhi = UDHI.parse(in.readInt());
    if (udhi == UDHI.HEADER_PRESENT) {
      this.udh = (int[])in.readObject();
    } else {
      this.udh = new int[0];
    }
    this.dcs = new DCS(in.readInt());
    this.udl = in.readInt();
    this.codec = (Codec)in.readObject();
  }
   
  /**
   * Extends the externalization of IntArrayBuilder.
   */
  @Override
  public void writeExternal(final ObjectOutput out) throws IOException {
    super.writeExternal(out);
    out.writeObject(this.toArray());
    out.writeInt(udhi.value);
    if (udhi == UDHI.HEADER_PRESENT) {
      out.writeObject(udh);
    }
    out.writeInt(dcs.getValue());
    out.writeInt(udl);
    out.writeObject(codec);
  }
  
  /**
   * @return [UDH:&lt;udh&gt;][&lt;message&gt;]
   */
  public String toString() {
    final StringBuilder sb = new StringBuilder(endpoint + udh.length + 10);
    if (getUDHI() == UDHI.HEADER_PRESENT) {
      sb.append("[UDH:");
      sb.append(Convert.intToHex(udh)); // TODO fix this when fixing udh
      sb.append(']');
    }
    sb.append('[');
    sb.append(getMessage());
    sb.append(']');
    return sb.toString();
  }
}